﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SharedUtils
{
    // Interpolation functions taken form : https://www.garagegames.com/community/forums/viewthread/60465
    public class Interpolation
    {
        public enum InterpolationMode
        {
            LINEAR,
            SIGMOID,
            CUBIC_EASEIN,
            CUBIC_EASEOUT, // BROKEN??
            BOUNCE_EASEIN, // BROKEN??
            BOUNCE_EASEOUT,
            ELASTIC_EASEOUT,
            SINE_EASEIN,
            SINE_EASEOUT,
            NONE
        }

        /// <summary>
        /// Interpolate Arbitrator
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        public static float interpolate(float from, float to, float delta, InterpolationMode mCameraInterpolationMode)
        {
            // Clamp if we're over/under time
            if (delta <= 0.0f)
                return from;
            else if (delta >= 1.0f)
                return to;

            // Linear
            switch (mCameraInterpolationMode)
            {
                case InterpolationMode.LINEAR:
                    // Linear
                    return linearInterpolate(from, to, delta);
                case InterpolationMode.SIGMOID:
                    // Sigmoid
                    return sigmoidInterpolate(from, to, delta);
                case InterpolationMode.CUBIC_EASEIN:
                    // cubic ease-in
                    return cubicEaseInInterpolate(from, to, delta);
                case InterpolationMode.CUBIC_EASEOUT:
                    // cubic ease-out
                    return cubicEaseOutInterpolate(from, to, delta);
                case InterpolationMode.BOUNCE_EASEIN:
                    // bounce ease-in
                    return bounceEaseInInterpolate(from, to, delta);
                case InterpolationMode.BOUNCE_EASEOUT:
                    // bounce ease-out
                    return bounceEaseOutInterpolate(from, to, delta);
                case InterpolationMode.ELASTIC_EASEOUT:
                    // elastic ease-out
                    return elasticEaseOutInterpolate(from, to, delta);
                case InterpolationMode.SINE_EASEIN:
                    // sine ease-in
                    return sineEaseInInterpolate(from, to, delta);
                case InterpolationMode.SINE_EASEOUT:
                    // sine ease-out
                    return sineEaseOutInterpolate(from, to, delta);
            }
            return from;
        }

        /// <summary>
        /// Linear Interpolate.
        /// Standard Linear Ramp Interpolation Function
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float linearInterpolate(float from, float to, float delta)
        {
            //Calculate resultant interpolation
            return from + (to - from) * delta;
        }

        /// <summary>
        /// Sigmoid Interpolate.
        /// Slow-in/ Slow-out Sigmoid Interpolation Function
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float sigmoidInterpolate(float from, float to, float delta)
        {
            // Range Expand/Clamp Delta to (-1 -> +1)
            delta = MathHelper.Clamp((delta - 0.5f) * 2.0f, -1.0f, 1.0f);

            //Calculate interpolator value using sigmoid function.
            float sigmoid = MathHelper.Clamp(1.0f / (1.0f + (float)Math.Pow(2.718282f, -15.0f * delta)), 0.0f, 1.0f);

            //Calculate resultant interpolation
            return from + (to - from) * sigmoid;
        }

        /// <summary>
        /// Cubic easeIn Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float cubicEaseInInterpolate(float from, float to, float delta)
        {

            return (to - from) * (float)Math.Pow(delta, 3.0f) + from;
        }

        /// <summary>
        /// Cubic easeOut Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float cubicEaseOutInterpolate(float from, float to, float delta)
        {
            delta = delta - 1;
            return (to - from) * (float)Math.Pow(delta, 3.0f) + from;
        }

        /// <summary>
        /// Bouce EaseIn Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float bounceEaseInInterpolate(float from, float to, float delta) {
            float change = to - from;
            return change - bounceEaseInInterpolate(0, change, 1 - delta) + from;
        }

        /// <summary>
        /// Bouce EaseOut Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float bounceEaseOutInterpolate(float from, float to, float delta)
        {
            float change = to - from;
            if (delta < (1.0f / 2.75f)) 
            {
                return change * (7.5625f * (float)Math.Pow(delta, 2)) + from;
            }
            else if (delta < (2.0f / 2.75f)) 
            {
                return change * (7.5625f * (delta-=(1.5f/2.75f))*delta + 0.75f) + from;
            }
            else if (delta < 2.5f / 2.75f)
            {
                return change * (7.5625f * (delta -= (2.25f / 2.75f)) * delta + 0.9375f) + from;
            }
            else 
            {
                return change * (7.5625f * (delta -= (2.625f / 2.75f)) * delta + 0.984375f) + from;
            }
        }

        /// <summary>
        /// Elastic EaseOut Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float elasticEaseOutInterpolate(float from, float to, float delta)
        {
            float p = 0.3f;
            float amplitude = 1.0f; // ??
            float change = to - from;
            float s = p / 4.0f;
            return (change * (float)Math.Pow(2.0f, -10.0f * delta) * (float)Math.Sin((delta * 1 - s) * (2.0f * Math.PI) / p) + change + from);
        }

        /// <summary>
        /// Sine EaseIn Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float sineEaseInInterpolate(float from, float to, float delta)
        {
            float change = (to-from);
            return -change * (float)Math.Cos(delta * (Math.PI/2)) + change + from;
        }

        /// <summary>
        /// Sine EaseOut Interpolate.
        /// </summary>
        /// <param name="from">Position from which we are interpolating.</param>
        /// <param name="to">Position to which we are interpolating.</param>
        /// <param name="delta">Time of interpolation</param>
        private static float sineEaseOutInterpolate(float from, float to, float delta)
        {
            return (to-from) * (float)Math.Sin(delta * (Math.PI/2)) + from;
        }
    }
}
